using System;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.nioneo.xa
{


	using InvalidRecordException = org.neo4j.kernel.impl.nioneo.store.InvalidRecordException;
	using NeoStore = org.neo4j.kernel.impl.nioneo.store.NeoStore;
	using NodeRecord = org.neo4j.kernel.impl.nioneo.store.NodeRecord;
	using NodeStore = org.neo4j.kernel.impl.nioneo.store.NodeStore;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using PropertyIndexData = org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
	using PropertyIndexRecord = org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord;
	using PropertyIndexStore = org.neo4j.kernel.impl.nioneo.store.PropertyIndexStore;
	using PropertyRecord = org.neo4j.kernel.impl.nioneo.store.PropertyRecord;
	using PropertyStore = org.neo4j.kernel.impl.nioneo.store.PropertyStore;
	using PropertyType = org.neo4j.kernel.impl.nioneo.store.PropertyType;
	using Record = org.neo4j.kernel.impl.nioneo.store.Record;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using RelationshipData = org.neo4j.kernel.impl.nioneo.store.RelationshipData;
	using RelationshipRecord = org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
	using RelationshipStore = org.neo4j.kernel.impl.nioneo.store.RelationshipStore;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	internal class NeoReadTransaction
	{
		private readonly NeoStore neoStore;

		public NeoReadTransaction(NeoStore neoStore)
		{
			this.neoStore = neoStore;
		}

		private NodeStore getNodeStore()
		{
			return neoStore.getNodeStore();
		}

		private int getRelGrabSize()
		{
			return neoStore.getRelationshipGrabSize();
		}

		private RelationshipStore getRelationshipStore()
		{
			return neoStore.getRelationshipStore();
		}

		private PropertyStore getPropertyStore()
		{
			return neoStore.getPropertyStore();
		}

		public virtual bool nodeLoadLight(int nodeId)
		{
			return getNodeStore().loadLightNode(nodeId);
		}

		public virtual RelationshipData relationshipLoad(int id)
		{
			RelationshipRecord relRecord = getRelationshipStore().getLightRel(id);
			if (relRecord != null)
			{
				return new RelationshipData(id, relRecord.getFirstNode(), relRecord.getSecondNode(), relRecord.getType());
			}
			return null;
		}

		public virtual RelationshipChainPosition getRelationshipChainPosition(int nodeId)
		{
			NodeRecord nodeRecord = getNodeStore().getRecord(nodeId);
			int nextRel = nodeRecord.getNextRel();
			return new RelationshipChainPosition(nextRel);
		}

		public virtual IEnumerable <RelationshipData> getMoreRelationships(int nodeId, RelationshipChainPosition position)
		{
			int nextRel = position.getNextRecord();
			List<RelationshipData> rels = new List<RelationshipData>();
			for (int i = 0; i < getRelGrabSize() && nextRel != Record.NO_NEXT_RELATIONSHIP.intValue(); i++)
			{
				RelationshipRecord relRecord = getRelationshipStore().getChainRecord(nextRel);
				if (relRecord == null)
				{
				// return what we got so far
					position.setNextRecord(Record.NO_NEXT_RELATIONSHIP.intValue());
					return rels;
				}
				int firstNode = relRecord.getFirstNode();
				int secondNode = relRecord.getSecondNode();
				if (relRecord.inUse())
				{
					rels.Add(new RelationshipData(relRecord.getId(), firstNode, secondNode, relRecord.getType()));
				}
				else
				{
					i--;
				}
				if (firstNode == nodeId)
				{
					nextRel = relRecord.getFirstNextRel();
				}
				else if (secondNode == nodeId)
				{
					nextRel = relRecord.getSecondNextRel();
				}
				else
				{
					throw new InvalidRecordException("Node[" + nodeId + "] not part of firstNode[" + firstNode + "] or secondNode[" + secondNode + "]");
				}
			}
			position.setNextRecord(nextRel);
			return rels;
		}

		public virtual ArrayMap<int, PropertyData> relGetProperties(int relId)
		{
			RelationshipRecord relRecord = getRelationshipStore().getRecord(relId);
			if (!relRecord.inUse())
			{
				throw new InvalidRecordException("Relationship[" + relId + "] not in use");
			}
			int nextProp = relRecord.getNextProp();
			ArrayMap<int, PropertyData> propertyMap = new ArrayMap<int, PropertyData>(9, false, true);
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyStore().getLightRecord(nextProp);
				propertyMap.put(propRecord.getKeyIndexId(), new PropertyData(propRecord.getId(), propertyGetValueOrNull(propRecord)));
				nextProp = propRecord.getNextProp();
			}
			return propertyMap;
		}

		internal virtual ArrayMap<int, PropertyData> nodeGetProperties(int nodeId)
		{
			NodeRecord nodeRecord = getNodeStore().getRecord(nodeId);

			int nextProp = nodeRecord.getNextProp();
			ArrayMap<int, PropertyData> propertyMap = new ArrayMap<int, PropertyData>(9, false, true);
			while (nextProp != Record.NO_NEXT_PROPERTY.intValue())
			{
				PropertyRecord propRecord = getPropertyStore().getLightRecord(nextProp);
				propertyMap.put(propRecord.getKeyIndexId(), new PropertyData(propRecord.getId(), propertyGetValueOrNull(propRecord)));
				nextProp = propRecord.getNextProp();
			}
			return propertyMap;
		}

		public virtual object propertyGetValueOrNull(PropertyRecord propertyRecord)
		{
			PropertyType type = propertyRecord.getType();
			if (type == PropertyType.INT)
			{
				return (int) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.STRING)
			{
				return null;
			}
			if (type == PropertyType.BOOL)
			{
				if (propertyRecord.getPropBlock() == 1)
				{
					return Convert.ToBoolean(true);
				}
				return Convert.ToBoolean(false);
			}
			if (type == PropertyType.DOUBLE)
			{
				return new double(double.longBitsToDouble(propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.FLOAT)
			{
				return new float(float.intBitsToFloat((int) propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.LONG)
			{
				return propertyRecord.getPropBlock();
			}
			if (type == PropertyType.BYTE)
			{
				return (sbyte) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.CHAR)
			{
				return (char) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.ARRAY)
			{
				return null;
			}
			if (type == PropertyType.SHORT)
			{
				return (short) propertyRecord.getPropBlock();
			}
			throw new InvalidRecordException("Unknown type: " + type + " on " + propertyRecord);
		}

		public virtual object propertyGetValue(int id)
		{
			PropertyRecord propertyRecord = getPropertyStore().getRecord(id);
			if (propertyRecord.isLight())
			{
				getPropertyStore().makeHeavy(propertyRecord);
			}
			PropertyType type = propertyRecord.getType();
			if (type == PropertyType.INT)
			{
				return (int) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.STRING)
			{
				return getPropertyStore().getStringFor(propertyRecord);
			}
			if (type == PropertyType.BOOL)
			{
				if (propertyRecord.getPropBlock() == 1)
				{
					return Convert.ToBoolean(true);
				}
				return Convert.ToBoolean(false);
			}
			if (type == PropertyType.DOUBLE)
			{
				return new double(double.longBitsToDouble(propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.FLOAT)
			{
				return new float(float.intBitsToFloat((int) propertyRecord.getPropBlock()));
			}
			if (type == PropertyType.LONG)
			{
				return propertyRecord.getPropBlock();
			}
			if (type == PropertyType.BYTE)
			{
				return (sbyte) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.CHAR)
			{
				return (char) propertyRecord.getPropBlock();
			}
			if (type == PropertyType.ARRAY)
			{
				return getPropertyStore().getArrayFor(propertyRecord);
			}
			if (type == PropertyType.SHORT)
			{
				return (short) propertyRecord.getPropBlock();
			}
			throw new InvalidRecordException("Unkown type: " + type + " on " + propertyRecord);
		}

		internal virtual string getPropertyIndex(int id)
		{
			PropertyIndexStore indexStore = getPropertyStore().getIndexStore();
			PropertyIndexRecord index = indexStore.getRecord(id);
			if (index.isLight())
			{
				indexStore.makeHeavy(index);
			}
			return indexStore.getStringFor(index);
		}

		internal virtual PropertyIndexData[] getPropertyIndexes(int count)
		{
			PropertyIndexStore indexStore = getPropertyStore().getIndexStore();
			return indexStore.getPropertyIndexes(count);
		}
	}

}